package cn.yh.register.order.service.impl;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import cn.yh.register.common.constant.MqConst;
import cn.yh.register.common.helper.HttpRequestHelper;
import cn.yh.register.common.service.RabbitService;
import cn.yh.register.enums.OrderStatusEnum;
import cn.yh.register.enums.PaymentStatusEnum;
import cn.yh.register.order.service.WeiXinService;
import cn.yh.register.order.util.ConstantPropertiesUtils;
import cn.yh.register.vo.msm.MsmVo;
import cn.yh.register.vo.order.*;
import com.alibaba.fastjson.JSONObject;

import cn.yh.register.client.hosp.HospFeignClient;
import cn.yh.register.client.user.UserFeignClient;
import cn.yh.register.common.exception.RegisterException;
import cn.yh.register.common.result.ResultCodeEnum;
import cn.yh.register.model.order.OrderInfo;
import cn.yh.register.model.user.Patient;
import cn.yh.register.order.mapper.OrderInfoMapper;
import cn.yh.register.order.service.OrderService;
import cn.yh.register.vo.hosp.ScheduleOrderVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


/**
 * @author dell
 */
@Slf4j
@Transactional(rollbackFor = Exception.class)
@Service
public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderService {

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private HospFeignClient hospFeignClient;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private WeiXinService weiXinService;

//    @Autowired
//    private OrderInfoMapper orderInfoMapper;

    /**
     * 保存订单 根据前端传的排班编号 病人id获取到该订单的id
     * 前端拿到这个id 来展示订单详情 所以返回值是long型的id
     * @param scheduleId 排班编号
     * @param patientId  病人id
     * @return 订单信息
     */
    @Override
    public Long saveOrder(String scheduleId, Long patientId) {
        //订单服务调用用户服务获取就诊人信息
        Patient patient = userFeignClient.getPatientOrder(patientId);
        if (patient == null){
            throw new RegisterException(ResultCodeEnum.PARAM_ERROR);
        }
        //订单服务调用医院服务获取订单排班信息
        ScheduleOrderVo scheduleOrderVo = hospFeignClient.getScheduleOrderVo(scheduleId);
        if (scheduleOrderVo == null){
            throw new RegisterException(ResultCodeEnum.PARAM_ERROR);
        }
        //判断是否未到预约时间或过了预约时间
        if (new DateTime(scheduleOrderVo.getStartTime()).isAfterNow() || new DateTime(scheduleOrderVo.getEndTime()).isBeforeNow()) {
            throw new RegisterException(ResultCodeEnum.TIME_NO);
        }
        //判断是否还有号源
        if (scheduleOrderVo.getAvailableNumber() <= 0){
            throw new RegisterException(ResultCodeEnum.NUMBER_NO);
        }
        SignInfoVo signInfoVo = hospFeignClient.getSignInfoVo(scheduleOrderVo.getHoscode());
        //2.2判断singInfoVo是否为空
        if (signInfoVo == null){
            throw new RegisterException(ResultCodeEnum.SIGN_ERROR);
        }
        //保存订单
        OrderInfo orderInfo = new OrderInfo();
        BeanUtils.copyProperties(scheduleOrderVo,orderInfo);
        // 订单编号
        String outTradeNo = System.currentTimeMillis() + String.valueOf(new Random().nextInt(100));
        orderInfo.setOutTradeNo(outTradeNo);
        //就诊人信息
        orderInfo.setUserId(patient.getUserId());
        orderInfo.setPatientId(patientId);
        orderInfo.setPatientName(patient.getName());
        orderInfo.setPatientPhone(patient.getPhone());
        // 订单状态 0：预约成功，待支付
        orderInfo.setOrderStatus(OrderStatusEnum.UNPAID.getStatus());
        // 保存订单
        baseMapper.insert(orderInfo);
        log.info("【订单服务保存订单】：{}",JSONObject.toJSONString(orderInfo));
        //对接医院系统订单接口 下订单
        //1、把需要请求的参数信息组装起来
        Map<String,Object> paramMap = new HashMap<>();
        //1.1组装排班信息
        paramMap.put("hoscode",orderInfo.getHoscode());
        paramMap.put("depcode",orderInfo.getDepcode());
        paramMap.put("hosScheduleId",orderInfo.getHosScheduleId());
        paramMap.put("reserveDate",new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd"));
        paramMap.put("reserveTime", orderInfo.getReserveTime());
        paramMap.put("amount",orderInfo.getAmount());
        //1.2组装就诊人信息
        paramMap.put("name", patient.getName());
        paramMap.put("certificatesType",patient.getCertificatesType());
        paramMap.put("certificatesNo", patient.getCertificatesNo());
        paramMap.put("sex",patient.getSex());
        paramMap.put("birthdate", patient.getBirthdate());
        paramMap.put("phone",patient.getPhone());
        paramMap.put("isMarry", patient.getIsMarry());
        paramMap.put("provinceCode",patient.getProvinceCode());
        paramMap.put("cityCode", patient.getCityCode());
        paramMap.put("districtCode",patient.getDistrictCode());
        paramMap.put("address",patient.getAddress());
        //1.3组装联系人信息
        paramMap.put("contactsName",patient.getContactsName());
        paramMap.put("contactsCertificatesType", patient.getContactsCertificatesType());
        paramMap.put("contactsCertificatesNo",patient.getContactsCertificatesNo());
        paramMap.put("contactsPhone",patient.getContactsPhone());
        paramMap.put("timestamp", HttpRequestHelper.getTimestamp());
        //1.4获取签名key
        String sign = HttpRequestHelper.getSign(paramMap, signInfoVo.getSignKey());
        //1.5签名key组装进去
        paramMap.put("sign",sign);
        //2.发送请求
        String apiUrl = signInfoVo.getApiUrl();
        //2.1获取到医院系统返回来的数据  jsonObject  （code、message、data）
        JSONObject jsonObject = HttpRequestHelper.sendRequest(paramMap, apiUrl + "/order/submitOrder");
        //2.3判断是否获取成功
        if (jsonObject.getInteger("code")==200){
            //2.3.1 成功 取里面的数据
            JSONObject data = jsonObject.getJSONObject("data");
            //2.3.2 取date里面的业务数据字段 更新到我们的数据酷订单表中
            //取号地址 fetchAddress
            String fetchAddress = data.getString("fetchAddress");
            //fetchTime 取号时间
            String fetchTime = data.getString("fetchTime");
            //number  预约序号
            Integer number = data.getInteger("number");
            // hosRecordId  预约记录唯一标识
            String hosRecordId = data.getString("hosRecordId");
            // 2.3.3组装到orderInfo中
            orderInfo.setFetchAddress(fetchAddress);
            orderInfo.setFetchTime(fetchTime);
            orderInfo.setNumber(number);
            orderInfo.setHosRecordId(hosRecordId);
            //2.3.4更新订单
            baseMapper.updateById(orderInfo);
            //2.3.5 获取可预约数与剩余预约数
            // 可预约数
            Integer reservedNumber = data.getInteger("reservedNumber");
            // 剩余预约数
            Integer availableNumber = data.getInteger("availableNumber");
            //2.3.6 发送消息 更新号源(是指通过rabbitMq异步通过医院服务更新我们MongoDB中的号源)  短信通知用户
            //2.3.6.1
            //组装消息  就是要把这些字段封装到orderMqVo提供消息中让医院服务异步更新号源
            OrderMqVo orderMqVo = new OrderMqVo();
            orderMqVo.setReservedNumber(reservedNumber);
            orderMqVo.setAvailableNumber(availableNumber);
            orderMqVo.setScheduleId(scheduleId);
            //组装短信实体  发给用户的
            MsmVo msmVo = new MsmVo();
            msmVo.setPhone(patient.getPhone());
            //安排时间
            String reserveDate =
                    new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd")
                            + (orderInfo.getReserveTime()==0 ? "上午": "下午");
            // 短信内容
            Map<String,Object> param = new HashMap<String,Object>(){{
                put("title", orderInfo.getHosname()+"|"+orderInfo.getDepname()+"|"+orderInfo.getTitle());
                put("amount", orderInfo.getAmount());
                put("reserveDate", reserveDate);
                put("name", orderInfo.getPatientName());
                put("quitTime", new DateTime(orderInfo.getQuitTime()).toString("yyyy-MM-dd HH:mm"));
            }};
            msmVo.setParam(param);
            //短信实体封装到订单消息  这样医院服务也拿到了短信实体，然后医院服务通过rabbitMq产生消息供短信服务消费，通知短信服务发短信。
            orderMqVo.setMsmVo(msmVo);
            //发送  给医院服务提供消息，让医院服务消费
           rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_ORDER,MqConst.ROUTING_ORDER,orderMqVo);
            log.info("【订单服务发送更新号源消息】：{}",JSONObject.toJSONString(orderMqVo));
        }else {
            throw new RegisterException(jsonObject.getString("message"), ResultCodeEnum.FAIL.getCode());
        }
        //医院下单成功  更新订单数据
        return orderInfo.getId();
    }

    /**
     * 分页查询订单列表
     *
     * @param page    当前页
     * @param queryVo 封装查询对象
     * @return 分页信息
     */
    @Override
    public IPage<OrderInfo> selectPage(Page<OrderInfo> page, OrderQueryVo queryVo) {
        LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(!StringUtils.isEmpty(queryVo.getKeyword()), OrderInfo::getHosname, queryVo.getKeyword())
                .eq(!StringUtils.isEmpty(queryVo.getPatientId()), OrderInfo::getPatientId, queryVo.getPatientId())
                .like(!StringUtils.isEmpty(queryVo.getPatientName()), OrderInfo::getPatientName, queryVo.getPatientName())
                .eq(!StringUtils.isEmpty(queryVo.getOrderStatus()), OrderInfo::getOrderStatus, queryVo.getOrderStatus())
                .eq(!StringUtils.isEmpty(queryVo.getReserveDate()), OrderInfo::getReserveDate, queryVo.getReserveDate())
                .ge(!StringUtils.isEmpty(queryVo.getCreateTimeBegin()), OrderInfo::getCreateTime, queryVo.getCreateTimeBegin())
                .le(!StringUtils.isEmpty(queryVo.getCreateTimeEnd()), OrderInfo::getCreateTime, queryVo.getCreateTimeEnd());
        Page<OrderInfo> infoPage = baseMapper.selectPage(page, wrapper);
        infoPage.getRecords().forEach(this::packOrder);
        return infoPage;
    }

    /**
     * 返回订单详情
     *
     * @param orderId 订单id
     * @return 订单信息
     */
    @Override
    public OrderInfo show(Long orderId) {
        OrderInfo orderInfo = baseMapper.selectById(orderId);
        this.packOrder(orderInfo);
        return orderInfo;
    }

    /**
     * 后台获取订单详情
     *
     * @param orderId 订单id
     * @return 订单信息
     */
    @Override
    public Map<String, Object> getOrderInfo(Long orderId) {
       Map<String,Object>  map = new HashMap<>();
        OrderInfo orderInfo = baseMapper.selectById(orderId);
        this.packOrder(orderInfo);
        map.put("orderInfo",orderInfo);
        Patient patient = userFeignClient.getPatientOrder(orderInfo.getPatientId());
        map.put("patient",patient);
        return map;
    }

    /**
     * 如果是支付成功的用户 退款后 要取消订单
     *
     * @param orderId 订单id
     */
    @Override
    public Boolean cancelOrder(Long orderId) {
        //1.先查订单
        OrderInfo orderInfo = this.show(orderId);
        //2.判断是否过了医院的规则 超过不能取消
        if (new DateTime(orderInfo.getQuitTime()).isBeforeNow()){
            throw new RegisterException(ResultCodeEnum.CANCEL_ORDER_NO);
        }
        //3.告知医院系统 取消订单
        //3.1组装参数
        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("hoscode", orderInfo.getHoscode());
        paramMap.put("hosRecordId", orderInfo.getHosRecordId());
        paramMap.put("timestamp", HttpRequestHelper.getTimestamp());
        //3.2调医院服务获取签名
        SignInfoVo signInfoVo = hospFeignClient.getSignInfoVo(orderInfo.getHoscode());
        paramMap.put("sign", HttpRequestHelper.getSign(paramMap,signInfoVo.getSignKey()));
        //3.3请求医院接口
        JSONObject jsonObject = HttpRequestHelper.sendRequest(paramMap, signInfoVo.getApiUrl() + "/order/updateCancelStatus");
        //3.1处理数据
        if (jsonObject.getInteger("code")==200) {
            // 3.2成功 判断是否已经支付
            if (orderInfo.getOrderStatus().equals(OrderStatusEnum.PAID.getStatus())){
                // 3.3 已支付 调微信退款接口 退款
                try {
                    Boolean refund = weiXinService.refund(orderId);
                    if (!refund){
                        // fasle 退款失败
                        throw new RegisterException(ResultCodeEnum.CANCEL_ORDER_FAIL);
                    }
                } catch (Exception e) {
                    throw new RegisterException(ResultCodeEnum.CANCEL_ORDER_FAIL);
                }
            }
            // 4.退款成功 更新订单状态
            orderInfo.setOrderStatus(OrderStatusEnum.CANCLE.getStatus());
            orderInfo.setUpdateTime(new Date());
            baseMapper.updateById(orderInfo);
            // 5. 更新号源  & 发送短信通知
            //5.1 封装信息
            OrderMqVo orderMqVo = new OrderMqVo();
            orderMqVo.setScheduleId(orderInfo.getHosScheduleId());
            //5.2 封装MsmVo
            MsmVo msmVo = new MsmVo();
            msmVo.setPhone(orderInfo.getPatientPhone());
            //5.3 组装消息体
            String reserveDate = new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd") + (orderInfo.getReserveTime()==0 ? "上午": "下午");
            Map<String,Object> param = new HashMap<String,Object>(){{
                put("title", orderInfo.getHosname()+"|"+orderInfo.getDepname()+"|"+orderInfo.getTitle());
                put("reserveDate", reserveDate);
                put("name", orderInfo.getPatientName());
            }};
            msmVo.setParam(param);
            orderMqVo.setMsmVo(msmVo);
            rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_ORDER,MqConst.ROUTING_ORDER,orderMqVo);
            log.info("【订单服务发送取消预约更新号源消息】：{}",JSONObject.toJSONString(orderMqVo));
        }else{
            throw new RegisterException(jsonObject.getString("message"),ResultCodeEnum.FAIL.getCode());
        }
        return true;
    }

    /**
     * 订单服务给短信服务发信息去给用户就诊提醒
     */
    @Override
    public void patientTips() {
        //1.数据库中根据预约时间查询列表
        List<OrderInfo> orderInfoList = baseMapper.selectList(new LambdaQueryWrapper<OrderInfo>()
                .eq(OrderInfo::getReserveDate, new DateTime().toString("yyyy-MM-dd")));

        //2.遍历列表
        for (OrderInfo orderInfo : orderInfoList) {
            //2.1.封装信息
            MsmVo msmVo = new MsmVo();
            msmVo.setPhone(orderInfo.getPatientPhone());
            //2.2封装param
            String reserveDate = new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd") + (orderInfo.getReserveTime()==0 ? "上午": "下午");
            Map<String,Object> param = new HashMap<String,Object>(){{
                put("title", orderInfo.getHosname()+"|"+orderInfo.getDepname()+"|"+orderInfo.getTitle());
                put("reserveDate", reserveDate);
                put("name", orderInfo.getPatientName());
            }};
            msmVo.setParam(param);
            //2.2.发信息
            rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_MSM,MqConst.ROUTING_MSM_ITEM,msmVo);
        }

    }

    /**
     * 查询统计数据
     * @param orderCountQueryVo 封装查询对象
     * @return map集合
     */
    @Override
    public Map<String, Object> getCountMap(OrderCountQueryVo orderCountQueryVo) {
        Map<String,Object> map = new HashMap<>();
        List<OrderCountVo> orderCountVoList = baseMapper.selectOrderCount(orderCountQueryVo);
        List<String> dateList = orderCountVoList.stream().map(OrderCountVo::getReserveDate).collect(Collectors.toList());
        List<Integer> countList = orderCountVoList.stream().map(OrderCountVo::getCount).collect(Collectors.toList());
        map.put("dateList",dateList);
        map.put("countList",countList);
        return map;
    }

    /**
     * 处理订单状态
     * @param orderInfo 封装订单对象
     */
    private void packOrder(OrderInfo orderInfo){
        String orderStatusString = OrderStatusEnum.getStatusNameByStatus(orderInfo.getOrderStatus());
        orderInfo.getParam().put("orderStatusString",orderStatusString);
    }
}
